package shaw.wifi.server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import shaw.wifi.encryption.AesEncryption;

public class ShawWifiMessage {
	public ShawWifiMessage(int length) {
		this.length = length;
		
		header = new String[length];
		data = new byte[length][];
	}
	
	public static final int TYPE_AUTH = 0;
	public static final int TYPE_NETWORKSETTINGS = 1;
	public static final int TYPE_ERROR = 2;
	public static final int TYPE_ESTABLISHCONNECTION = 3;
	public static final int TYPE_GETAPPCRED = 4;
	
	
	public int type;
	public int length;
	public String header[];
	public byte[][] data;
	
	public void sendMessage(ObjectOutputStream os) throws IOException {
		//send the length and type of the message
		os.writeInt(length);
		os.writeInt(type);
		
		for(int i = 0; i<length; i++) {
			//write a header, the length of the next data object, then the data object
			os.writeUTF(header[i]);
			os.writeInt(data[i].length);
			os.write(data[i]);
		}
		
		//send message
		os.flush();
	}
	
	public static ShawWifiMessage readMessage(ObjectInputStream ois) throws IOException {
		//read length
		int length = ois.readInt();
		
		//create appropriatly lengthed message
		ShawWifiMessage msg = new ShawWifiMessage(length);
		//set message type
		msg.type = ois.readInt();
		
		for(int i = 0; i<length; i++) {
			//read a header
			msg.header[i] = ois.readUTF();
			//read the length of the next data object, allocate appropriate space
			int dlen = ois.readInt();
			msg.data[i] = new byte[dlen];
			//read data
			ois.read(msg.data[i], 0, dlen);
		}
		
		//return message with read values
		return msg;
	}
	
	public void sendEncryptedMessage(ObjectOutputStream oos, AesEncryption encrypt) throws IOException {
		byte[] plainBytes = this.toByteArray();
		byte[] cryptBytes = encrypt.encryptAes(plainBytes);
		
		byte[] lenBs = lenBytes(cryptBytes);
		oos.write(lenBs[0]);
		oos.write(lenBs[1]);
		
		oos.write(cryptBytes);
		oos.flush();
	}
	
	
	public static ShawWifiMessage readEncryptedMessage(ObjectInputStream ois, AesEncryption encrypt) throws IOException {
		byte b1 = ois.readByte();
		byte b2 = ois.readByte();
		
		int length = getLen(b1, b2);
		byte[] buffer = new byte[length];
		
		System.out.println("reading encrypted message of length " + length);
		
		try {
			ois.read(buffer);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		byte[] plainBytes = encrypt.decryptAes(buffer);
		
		return ShawWifiMessage.fromByteArray(plainBytes);
	}
	
	public static ShawWifiMessage fromByteArray(byte[] input) {
		//start at the beginning
		int cursor = 0;
		
		//read length and type
		int length = input[cursor]; cursor++;
		int type = input[cursor]; cursor++;
		
		System.out.println("length " + length);
		
		ShawWifiMessage msg = new ShawWifiMessage(length);
		msg.type = type;
		
		for(int i = 0; i<length; i++) {
			//read the length of the header
			int headerLen = getLen(input[cursor], input[cursor+1]); cursor += 2;
			System.out.println("header length: " + headerLen);
			//read the value of the header
			byte[] headerByte = new byte[headerLen];
			for(int j = 0; j<headerLen; j++) {
				headerByte[j] = input[cursor]; cursor++;
			}
			msg.header[i] = new String(headerByte);
			
			//read the length of the data
			int dataLen = getLen(input[cursor], input[cursor+1]); cursor += 2;
			//read the data
			byte[] dataByte = new byte[dataLen];
			for(int j = 0; j<dataLen; j++) {
				dataByte[j] = input[cursor]; cursor++;
			}
			msg.data[i] = dataByte;
		}
		
		return msg;
	}
	
	public byte[] toByteArray() {
		System.out.println("converted to byte array " + this.header[0]);
		
		byte[] buffer = new byte[10000];
		int cursor = 0;
		buffer[cursor] = (byte) length; cursor++;
		buffer[cursor] = (byte) type; cursor++;
		
		for(int i = 0; i<length; i++) {
			byte[] headerBytes = header[i].getBytes();
			addByteArrayLengthToBuffer(buffer, cursor, headerBytes); cursor += 2;
			for(int j = 0; j<headerBytes.length; j++) {
				buffer[cursor] = headerBytes[j]; cursor++;
			}
			
			addByteArrayLengthToBuffer(buffer, cursor, data[i]); cursor += 2;
			for(int j = 0; j<data[i].length; j++) {
				buffer[cursor] = data[i][j]; cursor++;
			}
		}
		
		
		byte[] result = new byte[cursor];		
		for(int i = 0; i<cursor; i++) {
			result[i] = buffer[i];
		}
		return result;
	}
	
	private static void addByteArrayLengthToBuffer(byte[] buffer, int cursor, byte[] ba) {
		byte lenB[] = lenBytes(ba);
		buffer[cursor] = lenB[0];
		buffer[cursor+1] = lenB[1];
	}

	private static short getLen(byte b1, byte b2) {
		return (short) ((b1 << 8) + (b2 & 0xff));
	}
	
	private static byte[] lenBytes(byte[] array) {
		short s = (short) array.length;
		byte[] result = new byte[2];
		result[0] = (byte) ((s & 0xFF00) >> 8);
		result[1] = (byte) (s & 0xFF);
		return result;
	}
}
